/* * Copyright 2010 Robert Csakany <robson@semmi.se>. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * under the License. */ package org.liveSense.service.thumbnailGenerator; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import javax.jcr.Binary; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.ReferenceCardinality; import org.apache.felix.scr.annotations.ReferencePolicy; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.commons.osgi.OsgiUtil; import org.apache.sling.event.EventUtil; import org.apache.sling.event.jobs.JobProcessor; import org.apache.sling.event.jobs.JobUtil; import org.apache.sling.jcr.api.SlingRepository; import org.apache.sling.jcr.resource.JcrResourceConstants; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.Event; import org.osgi.service.event.EventHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.jhlabs.image.ScaleFilter; @Component(label="%thumbnailGeneratorJobEventHandler.name", description="%thumbnailGeneratorJobEventHandler.description", immediate=true, metatype=true, policy=ConfigurationPolicy.OPTIONAL) @Service(value = org.osgi.service.event.EventHandler.class) @Property(name = "event.topics", value = { ThumbnailGeneratorResourceChangeListener.THUMBNAIL_GENERATE_TOPIC, ThumbnailGeneratorResourceChangeListener.THUMBNAIL_REMOVE_TOPIC }) public class ThumbnailGeneratorJobEventHandler implements JobProcessor, EventHandler { /** * default log */ private final Logger log = LoggerFactory .getLogger(ThumbnailGeneratorJobEventHandler.class); public static final String PARAM_THUMBNAIL_RESOLUTIONS = "thumbnailResolutions"; public static final String THUMBNAIL_RESOLUTION_50_0 = "50x0"; public static final String THUMBNAIL_RESOLUTION_100_0 = "100x0"; public static final String THUMBNAIL_RESOLUTION_200_0 = "200x0"; public static final String[] DEFAULT_THUMBNAIL_RESOLUTIONS = new String[] { THUMBNAIL_RESOLUTION_50_0, THUMBNAIL_RESOLUTION_100_0, THUMBNAIL_RESOLUTION_200_0 }; public static final String PARAM_THUMBNAIL_FOLDER = "thumbnailFolder"; public static final String DEFAULT_THUMBNAIL_FOLDER = "_thumbnails_"; @Property(name = PARAM_THUMBNAIL_RESOLUTIONS, label = "%resolutions.name", description = "%resolutions.description", value = { THUMBNAIL_RESOLUTION_50_0, THUMBNAIL_RESOLUTION_100_0, THUMBNAIL_RESOLUTION_200_0 }) private String[] thumbnailResolutions = DEFAULT_THUMBNAIL_RESOLUTIONS; @Property(name = PARAM_THUMBNAIL_FOLDER, label = "%folder", description = "%folder.description", value = DEFAULT_THUMBNAIL_FOLDER) private String thumbnailFolder = DEFAULT_THUMBNAIL_FOLDER; @Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC) SlingRepository repository; @Reference(cardinality=ReferenceCardinality.MANDATORY_UNARY, policy=ReferencePolicy.DYNAMIC) ResourceResolverFactory resourceResolverFactory; /** * Activates this component. * * @param componentContext * The OSGi <code>ComponentContext</code> of this component. */ protected void activate(ComponentContext componentContext) throws RepositoryException { // Setting up thumbnailResolutions thumbnailResolutions = OsgiUtil.toStringArray(componentContext .getProperties().get(PARAM_THUMBNAIL_RESOLUTIONS), DEFAULT_THUMBNAIL_RESOLUTIONS); // Setting up thumbnail folder thumbnailFolder = (String) componentContext.getProperties().get( PARAM_THUMBNAIL_FOLDER); } @Override public void handleEvent(Event event) { if (EventUtil.isLocal(event)) { JobUtil.processJob(event, this); } } @Override public boolean process(Event event) { Session session = null; ResourceResolver resourceResolver = null; try { String resourcePath = (String) event.getProperty("resourcePath"); session = repository.loginAdministrative(null); Map<String, Object> authInfo = new HashMap<String, Object>(); authInfo.put(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session); try { resourceResolver = resourceResolverFactory .getResourceResolver(authInfo); } catch (LoginException e) { log.error("Authentication error"); return false; } if (event .getTopic() .equals(ThumbnailGeneratorResourceChangeListener.THUMBNAIL_REMOVE_TOPIC)) { // remove deleteThumbnailsForImage(session, resourcePath); } else if (event .getTopic() .equals(ThumbnailGeneratorResourceChangeListener.THUMBNAIL_GENERATE_TOPIC)) { // insert Resource res = resourceResolver.getResource(resourcePath); if (ResourceUtil.isA(res, "nt:file")) { createThumbnailsForImage(res); } } if (session != null && session.isLive() && session.hasPendingChanges()) session.save(); return true; } catch (RepositoryException e) { log.error("RepositoryException: " + e); return false; } catch (Exception e) { log.error("Exception: " + e); return false; } finally { if (resourceResolver != null) resourceResolver.close(); if (session != null) session.logout(); } } public boolean createThumbnailsForImage(Resource resource) throws RepositoryException, Exception { try { log.info("Generating thumbnail for image "+resource.getPath()); Node node = null; if (resource != null) node = resource.adaptTo(Node.class); if (node == null) return false; // if thumbnail folder does not exists we generate it if (!node.getParent().hasNode(thumbnailFolder)) { node.getParent().addNode(thumbnailFolder, "thumbnail:thumbnailFolder"); } // session.save(); Node thumbnailFolderNode = node.getParent() .getNode(thumbnailFolder); // Removing thumbnail images NodeIterator iter = thumbnailFolderNode .getNodes(resource.getName() + "*"); while (iter.hasNext()) { Node rm = iter.nextNode(); if (rm.isNodeType("thumbnail:thumbnailImage") && rm.hasProperty("originalNodeLastModified") && rm.getProperty("originalNodeLastModified").getDate().equals(node .getNode("jcr:content").getProperty("jcr:lastModified").getDate())) { } else { log.info(" -> Removing old thumbnail: " + rm.getName()); rm.remove(); } } // Generating thumbnail images for (int i = 0; i < thumbnailResolutions.length; i++) { String[] reso = thumbnailResolutions[i].split("x"); int width, height; width = Integer.parseInt(reso[0]); height = Integer.parseInt(reso[1]); String thumbnailName = resource.getName() + "." + width + "." + height + ".jpg"; if (!thumbnailFolderNode.hasNode(thumbnailName)) { final BufferedImage src = ImageIO.read(node .getNode("jcr:content").getProperty("jcr:data") .getBinary().getStream()); if (src == null) { final StringBuffer sb = new StringBuffer(); for (String fmt : ImageIO.getReaderFormatNames()) { sb.append(fmt); sb.append(' '); } throw new IOException( "Unable to read image, registered formats: " + sb); } final double scale = (double) width / src.getWidth(); int destWidth = width; int destHeight = height > 0 ? height : new Double(scale * src.getHeight()).intValue(); log.info(" ---> Generating thumbnail, w={}, h={}", destWidth, destHeight); final BufferedImage dest = new BufferedImage(destWidth, destHeight, BufferedImage.TYPE_INT_RGB); ScaleFilter filter = new ScaleFilter(destWidth, destHeight); filter.filter(src, dest); final File tmp = File.createTempFile(getClass().getSimpleName(), resource.getName()+"."+Calendar.getInstance().getTimeInMillis()); try { FileOutputStream outs = new FileOutputStream(tmp); ImageIO.write(dest, "jpg", outs); outs.flush(); outs.close(); // Create thumbnail node and set the mandatory properties Node thumbnail = thumbnailFolderNode.addNode(thumbnailName, "thumbnail:thumbnailImage"); thumbnail.addNode("jcr:content", "nt:resource").setProperty( "jcr:data", new Binary() { InputStream is; @Override public InputStream getStream() throws RepositoryException { try { is = new FileInputStream(tmp); } catch (FileNotFoundException e) { log.error("IOError: ",e); } return is; } @Override public int read(byte[] b, long position) throws IOException, RepositoryException { return is.read(b, (int) position, 4096); } @Override public long getSize() throws RepositoryException { try { return is.available(); } catch (IOException e) { throw new RepositoryException(e); } } @Override public void dispose() { try { is.close(); } catch (Exception e) { log.error("Dispose error!"); } } }); thumbnail.getNode("jcr:content").setProperty("jcr:lastModified", Calendar.getInstance()); thumbnail.getNode("jcr:content").setProperty("jcr:mimeType", "image/jpg"); thumbnail.setProperty("originalNodePath", resource.getPath()); thumbnail.setProperty("originalNodeLastModified", node .getNode("jcr:content").getProperty("jcr:lastModified").getDate()); thumbnail.setProperty("width", destWidth); thumbnail.setProperty("height", destHeight); log.info(" -> generated name: " + thumbnail.getPath() + " Width: {} Height: {} ", Integer.toString(destWidth), Integer.toString(destHeight)); //session.save(); } catch (Exception e) { return false; } finally { if (tmp != null) { tmp.delete(); } } } } return true; } finally { } } public void deleteThumbnailsForImage(Session session, String resourcePath) throws RepositoryException, Exception { try { // if thumbnail folder not exists we deleting nodes from it if (resourcePath.startsWith("/")) resourcePath = resourcePath.substring(1); if (resourcePath.lastIndexOf("/")<=0) return; String parentPath = resourcePath.substring(0, resourcePath.lastIndexOf("/")); String imageName = resourcePath.substring(resourcePath.lastIndexOf("/")+1, resourcePath.length()-1); log.info("Delete thumbnail for image "+imageName+" at "+parentPath); Node node = null; if (session.getRootNode().hasNode(parentPath)) { node = session.getRootNode().getNode(parentPath); } if (node != null && node.hasNode(thumbnailFolder)) { Node thumbnailFolderNode = node.getNode( thumbnailFolder); // Removing thumbnail images NodeIterator iter = thumbnailFolderNode.getNodes(imageName + "*"); while (iter.hasNext()) { Node rm = iter.nextNode(); log.info(" -> Removing thumbnail: " + rm.getName()); rm.remove(); } } } catch (Exception e) { log.error("deleteThumbnailsForImage",e); } finally { } } }